Day 2 我們裝好環境、寫了第一支登入的測試,程式碼裡出現了 Page 這個參數,也在 npx playwright test 執行時看到瀏覽器自動被打開。在寫後續的測試之前,我們先來了解一下Browser、BrowserContext 以及 Page 之間的關係。
這個架構是 playwright 的基礎,先了解這個架構,這樣在之後碰到context、並行執行以及多分頁的測試的時候,就知道是在做什麼了。
Playwright 把「瀏覽器」拆成三層概念,一層包一層:
Browser(瀏覽器實例,例如一個 Chromium process)
└── BrowserContext(獨立的瀏覽情境,類似一個無痕視窗)
└── Page(分頁)
└── Frame(頁面內的 iframe,之後會再提到)
Browser 代表整個瀏覽器應用程式。它是最外層,也是成本最高的資源,因為啟動一個瀏覽器需要時間分配記憶體等資源,所以測試過程中只會啟動一次。
BrowserContext 是一個獨立的瀏覽情境,你可以想像成你手動打開瀏覽器的「無痕視窗」,它有自己的 cookies、localStorage、sessionStorage,在同一個 Browser 下每個 Context 之間完全隔離,彼此之間不共享對方的登入狀態或任何資料,方便測試不同登入角色的功能。重點是建立一個新的 Context 非常輕量也非常快,不需要重新啟動整個瀏覽器進程。在 Day2 跑 Playwright的內建測試時,你看到多個瀏覽器打開、執行測試後又關掉,看起來像是每次都重新建立啟動一個瀏覽器,但是實際上它並沒有重新啟動 Browser ,反而是去建立一個新的 BrowserContext,然後在這個新的 BrowserContext裡面去執行測試。
Page 就是一個瀏覽器分頁,是在 Playwright 中用來直接對網頁進行操作的主要物件,像是點擊按鈕、輸入文字等。一個 BrowserContext 底下可以開很多個 Page,這些 Page 會共享同一份 cookies 跟登入狀態,因為它們同屬一個 BrowserContext,但彼此是不同的分頁,可以各自導航到不同網址。
在 Day2 的測試程式碼中,test('...', async ({ page }) => {...}) 裡的 page 就是 Playwright Test 幫你自動建立好的,他會先啟動一個 Browser(或重用已經啟動的),開一個新的 BrowserContext,再從裡面開一個 Page 用來執行測試。這一系列預設動作都是 Playwright Test 內部幫我們完成的,但其實也可以自己建立,之後講 Fixture 的時候會再提到。
讓我們用一段簡單的程式碼實際驗證 BrowserContext的隔離性。在playwright-tests 目錄下面新增一個 browserTest.ts 檔案,並將下面程式碼貼入後,直接在終端機中輸入: npx tsx browserTest.ts:
import { chromium } from 'playwright';
async function run() {
// 1. 啟動瀏覽器 (實體行程,只啟動一次)
const browser = await chromium.launch({ headless: false });
// 2. 開兩個完全獨立的Context,就像兩個無痕視窗
const contextA = await browser.newContext();
const contextB = await browser.newContext();
const pageA = await contextA.newPage();
const pageB = await contextB.newPage();
await pageA.goto('http://localhost:8000/');
await pageB.goto('http://localhost:8000/');
// 只在 A 登入
await pageA.getByRole('textbox', { name: 'Username' }).fill('demo');
await pageA.getByRole('textbox', { name: 'Password' }).fill('demo');
await pageA.getByRole('button', { name: 'Sign in' }).click();
// 重新整理 B,B 依然停留在登入頁,因為 B 是另一個 Context,不共享登入狀態
await pageB.reload();
// 等待分頁 5 秒,好讓我們能清楚看到兩個分頁的差異
await pageA.waitForTimeout(5000);
await pageB.waitForTimeout(5000);
// 關閉分頁
await pageA.close();
await pageB.close();
// 3. 關閉瀏覽器
await browser.close();
}
run();
上面的程式碼在一開始啟動瀏覽器,並且建立兩個完全獨立的 Context,這兩個 Context 下的 Page一起到目標網頁的首頁,但是我們只在 A 頁面進行登入,當我們重置 B 頁面的時候,會發現他還在登錄頁面,但是A頁面已經登入成功且在功能頁面,最後我們再把所有的資源關閉。
這個例子很好的證實了 BrowserContext 的隔離性。
今天了解了 Playwright 的底層架構:Browser、BrowserContext、Page 的三層關係。這個架構的概念如果看起來有點抽象也沒關係,之後看到 context.newPage()都可以回頭對照今天講的原理。
下一篇要來試試 Playwright 內建的 Codegen 錄製工具,實際操作錄製一段測試程式碼。